#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

#include "NativeMath.h"


int unitsspanned(const int width) {
	return (width % BASE_TYPE_BITS) != 0 ? 
		width / BASE_TYPE_BITS + 1 : 
	width / BASE_TYPE_BITS;  
}

uintarray* alloc_uintarr(const int length)
{
	uintarray* arr = (uintarray*)malloc(sizeof(uintarray));	
	arr->values = (UINT*)calloc(length,sizeof(UINT));
	arr->length = length;
	return arr;
}

void free_uintarr(uintarray* arr) {
	free(arr->values);
	free(arr);
	arr = NULL;
}

ninteger* newint(const int width, const BOOL issigned) {
	int length = unitsspanned(width);
	ninteger* value = (ninteger*)malloc(sizeof(ninteger));      
	value->content = alloc_uintarr(length);
	value->width = width;
	value->issigned = issigned;
	return value;
}

void free_ninteger(const ninteger* value)
{
	free_uintarr(value->content);
	free(value);
}

ninteger* newintfromstr(const char* value)
{
	UINT newwidth = 0, i;
	uintarray* newcontent;
	const size_t slen = strlen(value);
	ninteger* ni = (ninteger*)malloc(sizeof(ninteger));      

	switch(getstrtype(value))
	{
	case strtype_bin:
		ni->issigned = FALSE;
		ni->width = slen - 2; /* excluding prefix */
		ni->content = alloc_uintarr(unitsspanned(ni->width));
		assignbinstr(value, ni);
		return ni;
	case strtype_dec:
		ni->issigned = value[0] == '-' ? TRUE : FALSE;
		ni->width = ni->issigned ? (slen - 1u) * 4u : slen * 4u;
		ni->content = alloc_uintarr(unitsspanned(ni->width));
		assigndecstr(value, ni);

		newwidth = ni->width;
		/* Remove any unnecessary bits */
		if (ni->issigned)
		{
			for (i = ni->width - 1; i >= 0; i--)
			{
				/* width must be at least 1 */
				if (getbit(ni, i) && newwidth > 2)
					newwidth--;
				else
					break;
			}

			/* include sign bit */
			newwidth++;
		}
		else
		{
			for (i = ni->width - 1; i >= 0; i--)
			{
				/* width must be at least 1 */
				if (!getbit(ni, i) && newwidth > 2)
					newwidth--;
				else
					break;
			}
		}

		assert(newwidth > 0);
		newcontent = alloc_uintarr(unitsspanned(newwidth));
		memcpy(newcontent->values, ni->content->values, newcontent->length * sizeof(UINT));

		free_uintarr(ni->content);
		ni->content = newcontent;
		ni->width = newwidth;
		return ni;
	case strtype_hex:
		ni->issigned = FALSE;
		ni->width = (slen - 2u) * 4u; // not including the leading '0x' prefix
		ni->content = alloc_uintarr(unitsspanned(ni->width));
		assignhexstr(value, ni);
		return ni;
	case strtype_invalid:
	default:
		assert(FALSE);
		return NULL;
	}
}

void negateinternal(const uintarray* a, uintarray* r)
{	
	int i;
	assert(r != NULL);

	for (i = 0; i < a->length; i++)
		r->values[i] = ~a->values[i];

	addinternal2(r, 1, r);
}

void negate(const ninteger* value, const ninteger* result)
{
	if (result->width != value->width)
		perror("width mismatch");

	negateinternal(value->content, result->content);

	if (result->width % BASE_TYPE_BITS != 0)
		result->content->values[result->content->length - 1] &= bitmasks[result->width % BASE_TYPE_BITS];
}

EXPORT void getrange(const ninteger* value, const int index1, const int index2, ninteger* result)
{
	const int difference = index2 - index1 + 1;
	const int units = difference / BASE_TYPE_BITS;
	const int pos = difference % BASE_TYPE_BITS;
	ninteger* r;

	if (index1 >= value->width || index2 >= value->width)
		perror("Index out of range");

	if (index2 < index1)
		perror("Second index argument must be greater than the first");
	

	if (difference > result->width)
		perror("Width of 'result' too small to hold result");

  
	r = newint(value->width, value->issigned);
	shr(value, index1, r);

	//

	if ((difference % BASE_TYPE_BITS) != 0)
		r->content->values[units] &= bitmasks[pos];

	//resize(r->content, result->width);
	free_uintarr(result->content);
	result->content = r->content;     

	free(r);
}

/* 
* Test the value of the bit at the specified position. Returns true if set; false 
* otherwise. May throw IndexOutOfRangeException if the specified position is negative
* or greater than or equal to the width of the integer.
*/
BOOL getbit(const ninteger* value, const UINT position) 
{
	int bitindex, arrayindex;

	if(position >= value->width || position < 0)
		perror("position out of range");

	arrayindex = position / BASE_TYPE_BITS;
	bitindex = position % BASE_TYPE_BITS;

	return ((value->content->values[arrayindex] & (1 << bitindex)) != 0);
}

/*
* Set the bit at the specified position. May throw IndexOutOfRangeException if the 
* specified position is negative or greater than or equal to the width of the integer.
*/
void setbit(const ninteger* target, const int position)
{
	int bitindex, arrayindex;

	if (position >= target->width || position < 0)
		perror("position out of range");

	arrayindex = position / BASE_TYPE_BITS;
	bitindex = position % BASE_TYPE_BITS;

	target->content->values[arrayindex] |= (UINT)(1 << bitindex);
}

/*
* Clear the bit at the specified position. May throw IndexOutOfRangeException 
* if the specified position is negative or greater than or equal to the width of the integer.
*/
void clrbit(const ninteger* target, const int position)
{
	int bitindex, arrayindex;

	if (position >= target->width || position < 0)
		perror("position out of range");

	arrayindex = position / BASE_TYPE_BITS;
	bitindex = position % BASE_TYPE_BITS;

	target->content->values[arrayindex] &= (UINT)~(1 << bitindex);
}

void signextend(const ninteger* value, const UINT position)
{
	int arrayindex, bitindex, t, s, i;
	UINT v;

	if(!value->issigned)
		perror("cannot sign extend unsigned integer");

	arrayindex = position / BASE_TYPE_BITS;
	bitindex = position % BASE_TYPE_BITS;

	t = (int)value->content->values[arrayindex];
	s = BASE_TYPE_BITS - bitindex - 1;

	/* signed extend 't'. First logically shifted 's' places to the left */
	/* then aritmetically 's' places to the right.                       */
	value->content->values[arrayindex] = (UINT)(t << s >> s);

	v = getbit(value, position) ? BASE_MAX_VALUE : 0;

	for(i = arrayindex + 1; i < value->content->length; i++)
		value->content->values[i] = v;
}

int toint(const ninteger* value)
{
	return (int)(value->content->values[0]);
}

UINT touint(const ninteger* value)
{
	return value->content->values[0];
}

void valuecopy(const ninteger* a, const ninteger* result)
{
	if (a->width > result->width)
	{
		// only copy as many bytes as the result can hold
		int bytes = result->content->length * sizeof(UINT);
		memcpy(result->content->values, a->content->values, bytes);
	}
	else
	{   
		int i;
		int bytes = MIN(a->content->length, result->content->length) * sizeof(UINT);
		memcpy(result->content->values, a->content->values, bytes);

		if (a->issigned && isnegative(a))
		{
			for (i = a->content->length; i < result->content->length; i++)
				result->content->values[i] = BASE_MAX_VALUE;
		}
		else
		{
			const int ldiff = result->content->length - a->content->length;
			if(ldiff > 0)
				memset(result->content->values + a->content->length, 0, ldiff * sizeof(UINT));
		}
	}

	if (result->issigned)
	{
		signextend(result, result->width - 1);
	}
	else if ((result->width % BASE_TYPE_BITS) != 0)
	{
		result->content->values[result->content->length - 1] &= bitmasks[result->width % BASE_TYPE_BITS];
	}
}